/* 
 * Character device driver
 * (C) 2021.5.28  flly <gitee.com/flly11>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/slab.h>

/* chardev device name */
#define CHRDEV_NAME "flly_chrdev"

/*character device Major number: Dynamic allocation */
#define CHRDEV_MAJOR 0

/*character device Minor number */
#define CHRDEV_MINOR 2

/* Dynamic major number */
static int chrdev_major;

/* Class model */
static struct class *chrdev_class;

/* Device model */
static struct device *chrdev_dev;

/* private data */
struct chrdev_pdata {
	int num;
};

/* open */
static int chrdev_open(struct inode *inode, struct file *filp)
{
	
	struct chrdev_pdata *pdata;
	printk("%s\n",__func__);
	/* allocate memory to pdata */
	pdata = kzalloc(sizeof(struct chrdev_pdata), GFP_KERNEL);
	if(!pdata) {
		printk(KERN_ERR"No free memory!\n");
		return -ENOMEM;
	}

	pdata->num = 0x91;

	/* store on file */
	filp->private_data = pdata;
	return 0; 
}

/* release */
static int chrdev_release(struct inode *inode, struct file *filp)
{
	
	struct chrdev_pdata *pdata = filp->private_data;
	printk("%s\n",__func__);
	/* Safe pointer */
	filp->private_data = NULL;
	
	/* free private data */
	kfree(pdata);
	return 0;
}

/* read */
static ssize_t chrdev_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
	
	struct chrdev_pdata *pdata = filp->private_data;
	printk("%s\n",__func__);
	if(copy_to_user(buf, pdata, len)) {
		printk(KERN_ERR "Unable copy data to user.\n");
		return -EINVAL;
	}
	return len;
}
	
/* wirte */
static ssize_t chrdev_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
	
	struct chrdev_pdata *pdata = filp->private_data;
	printk("%s\n",__func__);
	
	if (copy_from_user(pdata, buf, len)) {
		printk(KERN_ERR "Unable copy data from  user.\n");
		return -EINVAL;
	}
	
	printk("Data from user:%d\n", pdata->num);
	
	return len;	
	
}



/*file operation */
static struct file_operations chrdev_fops = {
	.owner   = THIS_MODULE,
	.open    = chrdev_open,
	.write   = chrdev_write,
	.read    = chrdev_read,
	.release = chrdev_release,
};

static int __init chrdev_init(void)
{
	int rvl;
	printk("%s\n",__func__);
	/* register character driver */	
	rvl = register_chrdev( CHRDEV_MAJOR, CHRDEV_NAME, &chrdev_fops);	
	if (rvl < 0) {
		printk(KERN_ERR "Unable to register character driver.\n");
		rvl = -ENODEV;
		goto err_chrdev;
	}
	chrdev_major = rvl;

	/* register into class */
	chrdev_class = class_create(THIS_MODULE, CHRDEV_NAME);
	if (IS_ERR(chrdev_class)) {
		printk(KERN_ERR "Unable to register class subsystem.\n");
		rvl = PTR_ERR(chrdev_class);
		goto err_class;
	}
	
	/* Register device into sysfs */
	chrdev_dev = device_create(chrdev_class, NULL, MKDEV(chrdev_major, CHRDEV_MINOR), NULL, CHRDEV_NAME);
	if (IS_ERR(chrdev_dev)) {
		printk(KERN_ERR "Unable to register device subsystem.\n");
		rvl = PTR_ERR(chrdev_dev);
		goto err_dev;
	}
	
	printk(KERN_INFO "%s -Major: %d Minor: %d\n", CHRDEV_NAME, chrdev_major, CHRDEV_MINOR);
	
	return 0;

err_dev:
	class_destroy(chrdev_class);
err_class:
	unregister_chrdev(chrdev_major, CHRDEV_NAME);
err_chrdev:
	return rvl;

}

static void __exit chrdev_exit(void)
{
	printk("%s\n",__func__);
	device_destroy(chrdev_class, MKDEV(chrdev_major, CHRDEV_MINOR));
	class_destroy(chrdev_class);
	unregister_chrdev(chrdev_major, CHRDEV_NAME);
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("flly <gitee.com/flly11>");
MODULE_DESCRIPTION("Character Device driver");
